#include "kernel.h"
#include "globals.h"
#include "pcb.h"
#include "messages.h"
#include "pcbqueue.h"
#include "context.h"
#include "memstack.h"
#include "debug.h"
#include "processes.h"

SINT32 kernel_send_message (UINT32 process_ID, VOID* MessageEnvelope) {

	MESSAGE* msg = (MESSAGE*) MessageEnvelope;
	PCB* destination_pcb;
	if(current_process->process_ID != TIMER_I_PROCESS) {
		msg->sender_process_ID = current_process->process_ID;
		msg->destination_process_ID = process_ID;
	}

	destination_pcb = get_pcb(msg->destination_process_ID);

	enqueue_message(destination_pcb, msg);

	if( destination_pcb->process_state == WAITING_FOR_MESSAGE ) {
		destination_pcb->process_state = READY;
		rpq_enqueue(destination_pcb);
		//Add premption
	} else if( destination_pcb->process_ID == UART_I_PROCESS && screen_ready ) {
		current_process->process_state = INTERRUPTED;
		save_proc = current_process;
		context_switch(current_process, get_pcb(UART_I_PROCESS));
	}
	return RTX_SUCCESS;
}

VOID* kernel_receive_message (UINT32 * sender_ID) {
	MESSAGE* msg = dequeue_message(current_process);
	while (msg == NULL && current_process->process_state != I_PROCESS)
	{
		current_process->process_state = WAITING_FOR_MESSAGE;
		process_switch();
		msg = dequeue_message(current_process);
	}
	if(msg != NULL) {
		*sender_ID = msg->sender_process_ID;
	}
	return (VOID*)msg;
}

VOID* kernel_request_memory_block () {
	while( mem_head == NULL && current_process->process_state != I_PROCESS) {
		current_process->process_state = BLOCKED_ON_RESOURCE;
		priority_enqueue(&blocked_on_resource_queue, current_process);
		process_switch();
	}

	return (VOID*)pop_memory();
}

SINT32 kernel_release_memory_block (VOID* MemoryBlock) {
	push((MEMORY*)MemoryBlock);
	PCB* pcb = priority_dequeue(&blocked_on_resource_queue);
	if(pcb != NULL) {
		pcb->process_state = READY;
		rpq_enqueue(pcb);
		//Add Premption here
	}

	return RTX_SUCCESS;
}

SINT32 kernel_release_processor () {
	current_process->process_state = READY;
	if(current_process != NULL_PROCESS) {
		rpq_enqueue(current_process);
	}
	process_switch();
	return RTX_SUCCESS;
}

SINT32 kernel_delayed_send (UINT32 process_ID, VOID* MessageEnvelope, UINT32 delay) {
	MESSAGE* msg = (MESSAGE*) MessageEnvelope;
	PCB* timer_pcb = get_pcb(TIMER_I_PROCESS);

	msg->sender_process_ID = current_process->process_ID;
	msg->destination_process_ID = process_ID;
	msg->number_of_clock_ticks = current_time + delay;

	//Enqueue into Timer-I_Process

	if(timer_pcb->message_queue_head == NULL) {
		timer_pcb->message_queue_head = msg;
		timer_pcb->message_queue_tail = msg;
		msg->next = NULL;
	} else {
		MESSAGE* prev = NULL;
		MESSAGE* next = NULL;

		next = timer_pcb->message_queue_head;
		while(next != NULL) {
			if(next->number_of_clock_ticks > msg->number_of_clock_ticks) {
				break;
			}
			prev = next;
			next = prev->next;
		}

		if(prev != NULL) {
			prev->next = msg;
		} else {
			timer_pcb->message_queue_head = msg;
		}
		msg->next = next;

		if(next == NULL) {
			timer_pcb->message_queue_tail = msg;
		}
	}

	return RTX_SUCCESS;
}

SINT32 kernel_set_process_priority (UINT32 process_ID, UINT16 priority) {
	PCB* pcb = get_pcb(process_ID);
	if( rpq_remove(pcb) == RTX_SUCCESS ) {
		pcb->priority = priority;
		rpq_enqueue(pcb);
		//Add Premptive stuff here
	}
	return RTX_SUCCESS;
}

UINT16 kernel_get_process_priority (UINT32 process_ID) {
	PCB* pcb = get_pcb(process_ID);

	return pcb->priority;
}
